{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 183,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "# simple neural network of one complex valued neuron\n",
    "# extended to use a periodic activation function\n",
    "import numpy"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 184,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "class neuralNetwork:\n",
    "    \n",
    "    def __init__(self, inputs, cats, periods):\n",
    "        # number of inputs\n",
    "        self.inputs = inputs\n",
    "        \n",
    "        # link weights matrix\n",
    "        self.w = numpy.random.normal(0.0, pow(1.0, -0.5), (inputs + 1))\n",
    "        self.w = numpy.array(self.w, ndmin=2, dtype='complex128')\n",
    "        self.w += 1j * numpy.random.normal(0.0, pow(1.0, -0.5), (inputs + 1))\n",
    "        \n",
    "        # testing overrride\n",
    "        #self.w = numpy.array([1.0 + 0.0j, 1.0 + 0.0j], ndmin=2, dtype='complex128')\n",
    "        \n",
    "        # number of output class categories\n",
    "        self.categories = cats\n",
    "        \n",
    "        # todo periodicity\n",
    "        self.periodicity = periods\n",
    "        \n",
    "        pass\n",
    "    \n",
    "    def z_to_class(self, z):\n",
    "        # first work out the angle, but shift angle from [-pi/2, +pi.2] to [0,2pi]\n",
    "        angle = numpy.mod(numpy.angle(z) + 2*numpy.pi, 2*numpy.pi)\n",
    "        # from angle to category\n",
    "        p = int(numpy.floor (self.categories * self.periodicity * angle / (2*numpy.pi)))\n",
    "        p = numpy.mod(p, self.categories)\n",
    "        return p\n",
    "\n",
    "    def class_to_angles(self, c):\n",
    "        # class to several angles due to periodicity, using bisector\n",
    "        angles = (c + 0.5 + (self.categories * numpy.arange(self.periodicity))) / (self.categories * self.periodicity) * 2 * numpy.pi\n",
    "        return angles\n",
    "    \n",
    "    def status(self):\n",
    "        print (\"w = \", self.w)\n",
    "        print (\"categories = \", self.categories)\n",
    "        print (\"periodicity = \", self.periodicity)\n",
    "        pass\n",
    "\n",
    "    def query(self, inputs_list):\n",
    "        # add bias input\n",
    "        inputs_list.append(1.0)\n",
    "        \n",
    "        # convert input to complex\n",
    "        inputs = numpy.array(inputs_list, ndmin=2, dtype='complex128').T\n",
    "        #print(\"inputs = \\n\", inputs)\n",
    "        \n",
    "        # combine inputs, weighted\n",
    "        z = numpy.dot(self.w, inputs)\n",
    "        #print(\"z = \", z)\n",
    "        \n",
    "        # map to output classes\n",
    "        o = self.z_to_class(z)\n",
    "        #print(\"output = \", o)\n",
    "        #print (\"\")\n",
    "        return o\n",
    "    \n",
    "    def train(self, inputs_list, target):\n",
    "        # add bias input\n",
    "        inputs_list.append(1.0)\n",
    "        \n",
    "        # convert inputs and outputs list to 2d array\n",
    "        inputs = numpy.array(inputs_list, ndmin=2, dtype='complex128').T\n",
    "\n",
    "        # combine inputs, weighted\n",
    "        z = numpy.dot(self.w, inputs)[0]\n",
    "        \n",
    "        # desired angle from trainging set\n",
    "        # first get all possible angles\n",
    "        desired_angles = self.class_to_angles(target)\n",
    "        \n",
    "        # potential errors errors\n",
    "        errors =  numpy.exp(1j*desired_angles) - z\n",
    "        # select smallest error\n",
    "        e = errors[numpy.argmin(numpy.abs(errors))]\n",
    "        \n",
    "        # dw = e * x.T / (x.x.T)\n",
    "        dw = (e * numpy.conj(inputs.T)) / (self.inputs + 1)\n",
    "        #print(\"dw = \", dw)\n",
    "        self.w += dw\n",
    "        #print(\"new self.w = \", self.w )\n",
    "        #print(\"test new self.w with query = \", self.query(inputs.T))\n",
    "        #print(\"--\")\n",
    "    pass"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 185,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "# create instance of neural network\n",
    "number_of_inputs = 784\n",
    "categories = 10\n",
    "periods = 3\n",
    "\n",
    "n = neuralNetwork(number_of_inputs, categories, periods)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 186,
   "metadata": {
    "collapsed": false,
    "scrolled": true
   },
   "outputs": [],
   "source": [
    "# load the mnist training data CSV file into a list\n",
    "training_data_file = open(\"mnist_dataset/mnist_train.csv\", 'r')\n",
    "training_data_list = training_data_file.readlines()\n",
    "training_data_file.close()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 187,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "# train the neural network\n",
    "\n",
    "# epochs is the number of times the training data set is used for training\n",
    "epochs = 5\n",
    "\n",
    "for e in range(epochs):\n",
    "    # go through all records in the training data set\n",
    "    for record in training_data_list:\n",
    "        # split the record by the ',' commas\n",
    "        all_values = record.split(',')\n",
    "        # scale and shift the inputs\n",
    "        inputs = (numpy.asfarray(all_values[1:]) / 255.0 * 0.99) + 0.01\n",
    "        inputs = inputs.tolist()\n",
    "        n.train(inputs, int(all_values[0]))\n",
    "        pass\n",
    "    pass"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 188,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "# load the mnist test data CSV file into a list\n",
    "test_data_file = open(\"mnist_dataset/mnist_test.csv\", 'r')\n",
    "test_data_list = test_data_file.readlines()\n",
    "test_data_file.close()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 189,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "# test the neural network\n",
    "\n",
    "# scorecard for how well the network performs, initially empty\n",
    "scorecard = []\n",
    "\n",
    "# go through all the records in the test data set\n",
    "for record in test_data_list:\n",
    "    # split the record by the ',' commas\n",
    "    all_values = record.split(',')\n",
    "    # correct answer is first value\n",
    "    correct_label = int(all_values[0])\n",
    "    # scale and shift the inputs\n",
    "    inputs = (numpy.asfarray(all_values[1:]) / 255.0 * 0.99) + 0.01\n",
    "    inputs = inputs.tolist()\n",
    "    # query the network\n",
    "    label = n.query(inputs)\n",
    "\n",
    "    # append correct or incorrect to list\n",
    "    if (label == correct_label):\n",
    "        # network's answer matches correct answer, add 1 to scorecard\n",
    "        scorecard.append(1)\n",
    "    else:\n",
    "        # network's answer doesn't match correct answer, add 0 to scorecard\n",
    "        scorecard.append(0)\n",
    "        pass\n",
    "    \n",
    "    pass"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 190,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "performance =  0.1172\n"
     ]
    }
   ],
   "source": [
    "# calculate the performance score, the fraction of correct answers\n",
    "scorecard_array = numpy.asarray(scorecard)\n",
    "print (\"performance = \", scorecard_array.sum() / scorecard_array.size)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.5.1"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 0
}
